1 /*
2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021
3 License:   [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License].
4 Authors: Marcelo S. N. Mancini
5 
6 	Copyright Marcelo S. N. Mancini 2018 - 2021.
7 Distributed under the CC BY-4.0 License.
8    (See accompanying file LICENSE.txt or copy at
9 	https://creativecommons.org/licenses/by/4.0/
10 */
11 /**
12 *    This file provides the essential information for specifying vertices
13 *   for the target 3D API. Its Attributes/Layout, some preset layouts.
14 *    The workflow for vertices are entirely based on OpenGL, using VAOs and VBOs
15 *
16 */
17 
18 module hip.hiprenderer.vertex;
19 import hip.hiprenderer.renderer;
20 import hip.error.handler;
21 import hip.console.log;
22 public import hip.hiprenderer.backend.gl.glvertex;
23 // version(Android){alias index_t = ushort;}
24 // else{alias index_t = uint;}
25 alias index_t = ushort;
26 
27 
28 index_t index_t_maxQuads()
29 {
30     return cast(index_t)(ushort.max / 6);
31 }
32 index_t index_t_maxQuadIndices()
33 {
34     return cast(index_t)(index_t_maxQuads * 6);
35 }
36 
37 
38 
39 enum InternalVertexAttribute
40 {
41     POSITION = 0,
42     TEXTURE_COORDS,
43     COLOR
44 }
45 
46 enum InternalVertexAttributeFlags
47 {
48     POSITION = 1 << InternalVertexAttribute.POSITION,
49     TEXTURE_COORDS = 1 << InternalVertexAttribute.TEXTURE_COORDS,
50     COLOR = 1 << InternalVertexAttribute.COLOR,
51 }
52 enum HipBufferUsage
53 {
54     DYNAMIC,
55     STATIC,
56     DEFAULT
57 }
58 
59 enum HipAttributeType
60 {
61     ///Used as a unsigned r8g8b8a8 normalized type.
62     Rgba32,
63     Float,
64     Uint,
65     Int,
66     Bool
67 }
68 
69 
70 struct HipVertexAttributeInfo
71 {
72     uint index;
73     uint count;
74     uint offset;
75     uint typeSize;
76     HipAttributeType valueType;
77     string name;
78 }
79 
80 
81 interface IHipVertexBufferImpl
82 {
83     void bind();
84     void unbind();
85     void setData(const void[] data);
86     void updateData(int offset, const void[] data);
87 }
88 interface IHipIndexBufferImpl
89 {
90     void bind();
91     void unbind();
92     void setData(const index_t[] data);
93     void updateData(int offset, const index_t[] data);
94 }
95 interface IHipVertexArrayImpl
96 {
97     void bind(IHipVertexBufferImpl vbo, IHipIndexBufferImpl ebo);
98     void unbind(IHipVertexBufferImpl vbo, IHipIndexBufferImpl ebo);
99     void setAttributeInfo(ref HipVertexAttributeInfo info, uint stride);
100     ///Was created because Direct3D 11 needs shader to create its VAO
101     void createInputLayout(Shader s);
102 }
103 
104 
105 /**
106 *   For using this class, you must first define the vertex layout for after that, create the vertex
107 *   buffer and/or the index buffer.
108 */
109 class HipVertexArrayObject
110 {
111     IHipVertexArrayImpl  VAO;
112     IHipVertexBufferImpl VBO;
113     IHipIndexBufferImpl  EBO;
114     ///Accumulated size of the vertex data
115     uint stride;
116     ///How many data slots it uses, for instance, vec3 will count +3
117     uint dataCount;
118     HipVertexAttributeInfo[] infos;
119 
120     protected bool isBonded;
121     protected bool hasVertexInitialized;
122     protected bool hasIndexInitialized;
123     
124     /**
125     *   Remember calling sendAttributes
126     */
127     this()
128     {
129         isBonded = false;
130         this.VAO = HipRenderer.createVertexArray();
131     }
132 
133     /**
134     *   Populates a buffer with indices forming quads
135     *   Returns if the output can contain the size
136     */
137     static bool putQuadBatchIndices(ref index_t[] output, size_t countQuads)
138     {
139         assert(output.length >= countQuads*6, "Out of bounds");
140         if(output.length < countQuads*6)
141             return false;
142         index_t index = 0;
143         for(index_t i = 0; i < countQuads; i++)
144         {
145             output[index+0] = cast(index_t)(i*4+0);
146             output[index+1] = cast(index_t)(i*4+1);
147             output[index+2] = cast(index_t)(i*4+2);
148 
149             output[index+3] = cast(index_t)(i*4+2);
150             output[index+4] = cast(index_t)(i*4+3);
151             output[index+5] = cast(index_t)(i*4+0);
152             index+=6;
153         }
154         return true;
155     }
156 
157     /**
158     *   Creates and binds an index buffer.
159     */
160     void createIndexBuffer(index_t count, HipBufferUsage usage)
161     {
162         this.EBO = HipRenderer.createIndexBuffer(count, usage);
163         this.bind();
164         this.EBO.bind();
165     }
166     /**
167     * Creates and binds a vertex buffer.
168     *
169     * The vertex buffer size is dependant on the attributes that were appended to this vertex array.
170     */
171     void createVertexBuffer(uint count, HipBufferUsage usage)
172     {
173         this.VBO = HipRenderer.createVertexBuffer(count*this.stride, usage);
174         this.bind();
175         this.VBO.bind();
176     }
177     /**
178     *   This function creates an attribute information,
179     * for later sending it(it is necessary as the stride needs to be recalculated)
180     */
181     HipVertexArrayObject appendAttribute(
182         uint count, 
183         HipAttributeType valueType, 
184         uint typeSize, 
185         string infoName, 
186         bool isPadding = false,
187     )
188     {
189         HipVertexAttributeInfo info;
190         info.name = infoName;
191         info.count = count;
192         info.valueType = valueType;
193         info.typeSize = typeSize;
194         info.index = cast(uint)infos.length;
195         //It actually is the `last stride`, which is the same as the offset is the total current stride
196         info.offset = stride;
197         // if(!isPadding)
198         {
199             infos~= info;
200             dataCount+= count;
201         }
202         stride+= count*typeSize;
203         return this;
204     }
205 
206     HipVertexArrayObject appendAttribute(T)(string infoName, bool isPadding = false)
207     {
208         uint count = 1;
209         HipAttributeType type = HipAttributeType.Float;
210         uint typeSize = float.sizeof;
211         import hip.math.vector;
212 
213         static if(is(T == Vector2)) count = 2;
214         else static if(is(T == Vector3)) count = 3;
215         else static if(is(T == Vector4) || is(T == HipColorf)) count = 4;
216         else static if(is(T == HipColor))
217         {
218             type = HipAttributeType.Rgba32;
219             count = 4;
220             typeSize = ubyte.sizeof;
221         }
222         else
223         {
224             static if(is(T == int)) type = HipAttributeType.Int;
225             else static if(is(T == uint)) type = HipAttributeType.Uint;
226             else static if(is(T == bool)) type = HipAttributeType.Bool;
227             else
228                 static assert(is(T == float), "Unrecognized type for attribute: "~T.stringof);
229 
230             typeSize = T.sizeof;
231         }
232         return appendAttribute(count, type, typeSize ,infoName, isPadding);
233     }
234 
235     /**
236     *   Sets the attribute infos that were appended to this object. This function must only be called
237     *   after binding/creating a VBO, or it will fail
238     */
239     void sendAttributes(Shader s)
240     {
241         if(!isBonded)
242         {
243             ErrorHandler.showErrorMessage("VertexArrayObject error", "VAO wasn't bound when trying to send its attributes");
244             return;
245         }
246         foreach(info; infos)
247             this.VAO.setAttributeInfo(info, stride);
248         this.VAO.createInputLayout(s);
249     }
250 
251     void bind()
252     {
253         // if(!this.isBonded)
254         // {
255             isBonded = true;
256             this.VAO.bind(this.VBO, this.EBO);
257         // }
258     }
259     void unbind()
260     {
261         // if(this.isBonded)
262         // {
263             isBonded = false;
264             this.VAO.unbind(this.VBO, this.EBO);
265         // }
266     }
267 
268     /**
269     *   Sets the VBO data. Use this function only for initialization as it allocates memory.
270     *
271     *   If you wish to only update its data, call updateVertices instead.
272     */
273     void setVertices(const void[] data)
274     {
275         if(VBO is null)
276             ErrorHandler.showErrorMessage("Null VertexBuffer", "No vertex buffer was created before setting its vertices");
277         else
278         {
279             hasVertexInitialized = true;
280             this.bind(); 
281             this.VBO.setData(data);
282         }
283     }
284     /** 
285      * Update the VBO. Won't cause memory allocation.
286      * Params:
287      *   count = How many vertices to update
288      *   data = The data containing a type which is conforming to the VAO.
289      *   offset = The offset is always multiplied by this vertex array object stride.
290      */
291     void updateVertices(const void[] data, int offset = 0)
292     {
293         if(VBO is null)
294             ErrorHandler.showErrorMessage("Null VertexBuffer", "No vertex buffer was created before setting its vertices");
295         ErrorHandler.assertExit(hasVertexInitialized, "Vertex must setData before updating its contents.");
296         this.bind();
297         this.VBO.updateData(offset*this.stride, data);
298     }
299     /**
300     *   Will set the indices data. Beware that this function may allocate memory.
301     *   
302     *   If you need to only change its data value instead of allocating memory for a greater index buffer
303     *   call updateIndices
304     */
305     void setIndices(const index_t[] data)
306     {
307         if(EBO is null)
308             ErrorHandler.showErrorMessage("Null IndexBuffer", "No index buffer was created before setting its indices");
309         else
310         {
311             hasIndexInitialized = true;
312             this.bind();
313             this.EBO.setData(data);
314         }
315     }
316     /**
317     *   Updates the index buffer's data. It won't allocate memory
318     */
319     void updateIndices(const index_t[] data, int offset = 0)
320     {
321         if(EBO is null)
322             ErrorHandler.showErrorMessage("Null IndexBuffer", "No index buffer was created before setting its indices");
323         else
324         {
325             ErrorHandler.assertExit(hasIndexInitialized, "Index must setData before updating its contents.");
326             this.bind();
327             this.EBO.updateData(cast(int)(offset*index_t.sizeof), data);
328         }
329     }
330 
331     /**
332     * Receives a struct and creates a VAO based on its member types and names.
333     */
334     static HipVertexArrayObject getVAO(T)() if(is(T == struct))
335     {
336         import std.traits:isFunction;
337         import hip.util.reflection:hasUDA;
338 
339         HipVertexArrayObject obj = new HipVertexArrayObject();
340         static foreach(member; __traits(allMembers, T))
341         {{
342             alias mem = __traits(getMember, T, member);
343             static if(!isFunction!(mem) && __traits(compiles, mem.offsetof))
344             {
345                 obj.appendAttribute!((typeof(mem)))
346                 (
347                     member,
348                     hasUDA!(mem, HipShaderInputPadding)
349                 );
350             }
351         }}
352         return obj;
353     }
354 
355 }